﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SoulHackerCUnite.Data;
using SoulHackerCUnite.Extensions;

namespace SoulHackerCUnite.Functions
{
    public class Unite
    {
        private List<UniteRule> _rules;

        public List<UniteRule> Rules
        {
            get { return _rules; }
            set
            {
                _rules2 = new List<UniteRule>();
                _rules3 = new List<UniteRule>();
                _rulesS = new List<UniteRule>();
                _rulesE = new List<UniteRule>();

                _rulesM = new List<UniteRule>();
                _rulesT = new List<UniteRule>();
                _rulesN = new List<UniteRule>();
                _rulesF = new List<UniteRule>();

                foreach (UniteRule _rule in value)
                {
                    if (_rule.Result == "－") // remove null
                        continue;

                    if (_rule.Result == "*" && _rule.Type == "D") // remove same as not dark
                        continue;

                    if (_rule.Type == "2")
                        _rules2.Add(_rule);
                    if (_rule.Type == "3")
                        _rules3.Add(_rule);
                    if (_rule.Type == "D")
                    {
                        _rules2.Add(_rule);
                        _rules3.Add(_rule);
                    }
                    if (_rule.Type == "S")
                        _rulesS.Add(_rule);
                    if (_rule.Type == "E")
                        _rulesE.Add(_rule);
                    if (_rule.Type == "M")
                        _rulesM.Add(_rule);
                    if (_rule.Type == "T")
                        _rulesT.Add(_rule);
                    if (_rule.Type == "N")
                        _rulesN.Add(_rule);
                    if (_rule.Type == "F")
                        _rulesF.Add(_rule);
                }
            }
        }

        private List<Demon> _demons;
        public List<Demon> Demons
        {
            get { return _demons; }
            set
            {
                _demons = value;

                _raceDemons = new Dictionary<string, List<Demon>>();
                foreach (Demon demon in value)
                {
                    if(_raceDemons.ContainsKey(demon.RaceJ))
                    {
                        
                    }
                    else
                    {
                        _raceDemons.Add(demon.RaceJ,new List<Demon>());
                    }
                    _raceDemons[demon.RaceJ].Add(demon);
                }

                foreach (KeyValuePair<string, List<Demon>> keyValuePair in _raceDemons)
                {
                    keyValuePair.Value.Sort((d1,d2)=>d1.Level.CompareTo(d2.Level));
                }
            }
        }

        private List<Race> _races;
        public List<Race> Races
        {
            get { return _races; }
            set 
            { 
                _races = value;
                _racePrec = new Dictionary<string, int>();
                foreach (Race race in value)
                {
                    _racePrec.Add(race.RaceName,race.UnitePrec);
                }
            }
        }

        private Dictionary<string, int> _racePrec;

        /*private List<UniteRule3> _rules3All;
        public List<UniteRule3> Rules3All
        {
            get
            {
                if(_rules3All==null)
                {
                    if (_racePrec != null && _racePrec.Count > 0 && _rules3 != null && _rules3.Count > 0 && _rules2 != null && _rules2.Count > 0)
                    {
                        _rules3All = new List<UniteRule3>();
                        foreach (UniteRule rule in _rules3)
                        {
                            UniteRule rule3 = rule;
                            List<UniteRule> rules2 = _rules2.FindAll(r => r.Result == rule3.Race1 && _racePrec[r.Race1] >= _racePrec[rule3.Race1] && _racePrec[r.Race2] >= _racePrec[rule3.Race1]);

                            _rules3All.Add(new UniteRule3(){Race1 = });
                        }
                    }
                }
                return _rules3All;
            }
        }*/

        private Dictionary<string, List<Demon>> _raceDemons;

        // 2 unite
        private List<UniteRule> _rules2;
        // 3 unite
        private List<UniteRule> _rules3;
        // element unite
        private List<UniteRule> _rulesE;
        // special unite
        private List<UniteRule> _rulesS;
        // mitama unite
        private List<UniteRule> _rulesM;
        // temp unite
        private List<UniteRule> _rulesT;
        // new moon unite()
        private List<UniteRule> _rulesN;
        // full moon unite()
        private List<UniteRule> _rulesF;

        private List<Skill> _skills;

        public List<Skill> Skills
        {
            get { return _skills; }
            set { _skills = value; }
        }

        private List<SkillInhert> _skillInherts;

        public List<SkillInhert> SkillInherts
        {
            get { return _skillInherts; }
            set { _skillInherts = value; }
        }

        private bool IsElement(Demon demon)
        {
            // not load race info, so use hardcode name
            return (demon.RaceJ == "精霊")||(_rulesT.Any(r=>r.Result == demon.JName)); 
        }

        public Demon Unite2(Demon demon1, Demon demon2)
        {
            Demon result = null;// = new Demon();
            if (_rules2 != null && _rules2.Count > 0 && Demons != null && Demons.Count > 0)
            {
                if (IsElement(demon1) && IsElement(demon2))
                    return UniteM(demon2, demon1);
                
                if (IsElement(demon1))
                    return UniteE(demon2, demon1);

                if (IsElement(demon2))
                    return UniteE(demon1, demon2);

                string resultRace = UniteRace2(demon1, demon2);
                int resultLevel = (demon1.Level + demon2.Level) / 2 + 3;
                List<Demon> partDemons;
                //unite to special demon
                partDemons = Demons.FindAll(d => d.JName == resultRace);
                if (partDemons.Count > 0)
                {
                    result = partDemons[0];
                }
                else
                {
                    partDemons = Demons.FindAll(d => d.RaceJ == resultRace && d.Level >= resultLevel);
                    if (partDemons.Count > 0)
                    {
                        partDemons.Sort((d1, d2) => d1.Level.CompareTo(d2.Level));
                        result = partDemons[0];
                    }
                    else
                    {
                        if(_raceDemons.ContainsKey(resultRace))
                        {
                            List<Demon> raceDemons = _raceDemons[resultRace];
                            // return the max level demon.
                            if (resultLevel > raceDemons[raceDemons.Count - 1].Level)
                                result = raceDemons[raceDemons.Count - 1];
                        } 
                    }
                }

                return this.UniteSkill(demon1, demon2, null, result);

            }
            /*else
            {
                return null;
            }*/
            return result;
        }

        private Demon UniteE(Demon demon, Demon element)
        {
            if (_rulesE != null && _rulesE.Count > 0 && Demons != null && Demons.Count > 0)
            {
                Demon result = null;
                if (IsElement(demon))
                {
                    List<UniteRule> findRule = _rulesT.FindAll(r => ((r.Race1 == demon.JName && r.Race2 == element.JName) || (r.Race2 == demon.JName && r.Race1 == element.JName)));
                    if (findRule.Count > 0)
                    {
                        List<Demon> raceDemons = Demons.FindAll(d => d.JName == findRule[0].Result);
                        if (raceDemons.Count > 0)
                        {
                            result = raceDemons[0];
                            return this.UniteSkill(demon, element, null, result);
                        }
                        else
                            result = new Demon() { JName = findRule[0].Result };
                    }
                    return result;
                }
                else
                {
                    List<UniteRule> findRule = _rulesE.FindAll(r => ((r.Race1 == demon.RaceJ && r.Race2 == element.JName)));
                    if (findRule.Count > 0)
                    {
                        List<Demon> raceDemons = Demons.FindAll(d => d.RaceJ == demon.RaceJ);
                        raceDemons.Sort((d1, d2) => d1.Level.CompareTo(d2.Level));
                        int index = raceDemons.FindIndex(d => d.JName == demon.JName);

                        if (findRule[0].Result == "○")
                            result = index < raceDemons.Count - 1 ? raceDemons[index + 1] : null;
                        else if (findRule[0].Result == "×")
                            result = index > 0 ? raceDemons[index - 1] : null;
                        else if (findRule[0].Result == "◎")
                            result = index < raceDemons.Count - 1 ? raceDemons[index + 2 >= raceDemons.Count ? index + 1 : index + 2] : null;

                    }
                    return this.UniteSkill(demon, element, null, result);
                }
            }
            return null;
        }

        private Demon UniteM(Demon element1, Demon element2)
        {
            if (_rulesM != null && _rulesM.Count > 0 && Demons != null && Demons.Count > 0)
            {
                if (IsElement(element1) && IsElement(element2))
                {
                    List<UniteRule> findRule = _rulesM.FindAll(r => ((r.Race1 == element1.JName && r.Race2 == element2.JName) || (r.Race2 == element1.JName && r.Race1 == element2.JName)));
                    if (findRule.Count > 0)
                    {
                        List<Demon> raceDemons = Demons.FindAll(d => d.JName == findRule[0].Result);
                        if (raceDemons.Count > 0)
                            return raceDemons[0];
                        else
                            return new Demon() { JName = findRule[0].Result };
                    }
                }
            }
            return null;
        }

        private string UniteRace2(Demon demon1, Demon demon2)
        {
            List<UniteRule> findRule;

            // element
            if (IsElement(demon1))
                return demon2.RaceJ;
            else if (IsElement(demon2))
                return demon1.RaceJ;

            // special unite
            findRule = _rulesS.FindAll(r => ((r.Race1 == demon1.JName && r.Race2 == demon2.JName) || (r.Race1 == demon2.JName && r.Race2 == demon1.JName)));
            if (findRule.Count > 0)
            {
                string resultRace = findRule[0].Result;
                return resultRace;
            }

            // common unite
            findRule = _rules2.FindAll(r => ((r.Race1 == demon1.RaceJ && r.Race2 == demon2.RaceJ) || (r.Race1 == demon2.RaceJ && r.Race2 == demon1.RaceJ)));
            if (findRule.Count > 0)
            {
                string resultRace = findRule[0].Result;
                return resultRace;
            }
            return string.Empty;
        }

        public Demon Unite3(Demon demon1, Demon demon2, Demon demon3)
        {
            Demon result = null;// = new Demon();
            if (_rules3 != null && _rules3.Count > 0 && Demons != null && Demons.Count > 0)
            {
                List<Demon> demons = new List<Demon>();
                demons.Add(demon1);
                demons.Add(demon2);
                demons.Add(demon3);
                demons.Sort((d1,d2)=>d1.UnitePrec.CompareTo(d2.UnitePrec));
                if (IsElement(demons[1]) && IsElement(demons[2]))
                {
                    Demon tempmi = UniteE(demons[1], demons[2]);
                    if (tempmi == null)
                        return null;
                    if (IsElement(demons[0]))
                        return UniteM(demons[0], tempmi);
                    else
                        return UniteE(demons[0], tempmi);
                }
                string race2 = UniteRace2(demons[2],demons[1]);
                List<UniteRule> findRule = _rules3.FindAll(r => ((r.Race1 == demons[0].RaceJ && r.Race2 == race2) || (r.Race1 == race2 && r.Race2 == demons[0].RaceJ)));
                if (findRule.Count > 0)
                {
                    string resultRace = findRule[0].Result;
                    int resultLevel = (demons[0].Level + demons[1].Level + demons[2].Level) / 3 + 4;
                    List<Demon> partDemons = Demons.FindAll(d => d.RaceJ == resultRace && d.Level >= resultLevel);
                    if (partDemons.Count > 0)
                    {
                        partDemons.Sort((d1, d2) => d1.Level.CompareTo(d2.Level));
                        result = partDemons[0];
                    }
                    else
                    {
                        if (_raceDemons.ContainsKey(resultRace))
                        {
                            List<Demon> raceDemons = _raceDemons[resultRace];
                            // return the max level demon.
                            if (resultLevel > raceDemons[raceDemons.Count - 1].Level)
                                result = raceDemons[raceDemons.Count - 1];
                        } 
                    }
                }

                return this.UniteSkill(demons[0], demons[1], demons[2], result);
            }
            /*else
            {
                return null;
            }*/
            return result;
        }

        /// <summary>
        /// We need try to find all Demons can be United with the given demons
        /// </summary>
        /// <param name="demons"></param>
        /// <param name="deep"></param>
        /// <returns></returns>
        public List<ResultToShow> UniteMuti(List<Demon> demons, int deep = 1)
        {
            List<ResultToShow> results = new List<ResultToShow>();
            List<Demon> uniteDemons = new List<Demon>();

            uniteDemons.AddRange(demons);
            for (int i = 0; i < demons.Count; i++)
            {
                for (int j = i + 1; j < demons.Count; j++)
                {
                    Demon result2 = this.Unite2(demons[i], demons[j]);
                    if(result2!=null)
                        results.CheckAdd(new ResultToShow(){Demon1 = demons[i],Demon2 = demons[j],Result = result2});
                    for(int k=j+1;k<demons.Count;k++)
                    {
                        Demon result3 = this.Unite3(demons[i], demons[j], demons[k]);
                        if (result3 != null)
                            results.CheckAdd(new ResultToShow() { Demon1 = demons[i], Demon2 = demons[j],Demon3 = demons[k], Result = result3, Plus2 = "+"});
                    }
                }
            }

            return results;
        }

        public List<ResultToShow> UnUnite2(Demon demon)
        {
            List<ResultToShow> results = new List<ResultToShow>();
            
            if (demon == null)
                return null;

            if(IsElement(demon))
            {
                return results;
            }

            List<UniteRule> rules;
            rules = _rules2.FindAll(r=>r.Result == demon.JName);
            if(rules.Count>0)
            {
                foreach (UniteRule uniteRule in rules)
                {
                    UniteRule rule = uniteRule;
                    results.Add(new ResultToShow() { Demon1 = Demons.Find(d => d.JName == rule.Race1), Demon2 = Demons.Find(d => d.JName == rule.Race2),Result = demon});
                }
            }

            rules = _rules2.FindAll(r=>r.Result == demon.RaceJ);

            List<Demon> raceDemons = _raceDemons[demon.RaceJ]; //Demons.FindAll(d => d.RaceJ == demon.RaceJ);
            raceDemons.Sort((d1,d2)=>d1.Level.CompareTo(d2.Level));
            int levelMax = demon.Level;
            int levelMin;
            int sortInRace = raceDemons.FindIndex(d => d.JName == demon.JName);
            levelMin = sortInRace > 1 ? raceDemons[sortInRace - 1].Level : 0;
            //to see if the max
            if (sortInRace == raceDemons.Count - 1)
                levelMax = 999;

            if(rules.Count>0)
            {
                foreach (UniteRule uniteRule in rules)
                {
                    List<Demon> demons1 = _raceDemons[uniteRule.Race1];
                    List<Demon> demons2 = _raceDemons[uniteRule.Race2];

                    for (int i = 0; i < demons1.Count; i++)
                    {
                        int j = 0;
                        for (; j < demons2.Count; j++)
                        {
                            int countLevel = (demons1[i].Level + demons2[j].Level)/2 + 3;
                            if(countLevel>levelMax)
                            {
                                break;
                            }
                            if(countLevel>levelMin)
                            {
                                if (!demons1[i].Equals(demons2[j]))
                                    results.Add(new ResultToShow(){Demon1 = demons1[i],Demon2 = demons2[j],Result = demon});
                            }
                        }
                        if (j == 0)
                            break;
                    }
                }
            }


            return results;
        }

        public List<ResultToShow> UnUnite3(Demon demon)
        {
            List<ResultToShow> results = new List<ResultToShow>();

            if (demon == null)
                return null;

            if (IsElement(demon))
            {
                return results;
            }

            List<UniteRule> rules;

            rules = _rules3.FindAll(r => r.Result == demon.RaceJ);

            List<Demon> raceDemons = _raceDemons[demon.RaceJ]; //Demons.FindAll(d => d.RaceJ == demon.RaceJ);
            raceDemons.Sort((d1, d2) => d1.Level.CompareTo(d2.Level));
            int levelMax = demon.Level;
            int levelMin;
            int sortInRace = raceDemons.FindIndex(d => d.JName == demon.JName);
            levelMin = sortInRace > 1 ? raceDemons[sortInRace - 1].Level : 0;
            //to see if the max
            if (sortInRace == raceDemons.Count - 1)
                levelMax = 999;

            if (rules.Count > 0)
            {                
                foreach (UniteRule uniteRule in rules)
                {
                    UniteRule rule3 = uniteRule;
                    List<UniteRule> rules2 = _rules2.FindAll(r => r.Result == rule3.Race1 && _racePrec[r.Race1] >= _racePrec[rule3.Race2] && _racePrec[r.Race2] >= _racePrec[rule3.Race2]);

                    foreach (UniteRule rule in rules2)
                    {
                        List<Demon> demons1 = _raceDemons[uniteRule.Race2];
                        List<Demon> demons2 = _raceDemons[rule.Race1];
                        List<Demon> demons3 = _raceDemons[rule.Race2];

                        for (int i = 0; i < demons1.Count; i++)
                        {
                            int j = 0;
                            for (; j < demons2.Count; j++)
                            {
                                int k = 0;
                                for (; k < demons3.Count; k++)
                                {
                                    int countLevel = (demons1[i].Level + demons2[j].Level + demons3[k].Level)/3 + 4;
                                    if (countLevel > levelMax)
                                    {
                                        break;
                                    }
                                    if (countLevel > levelMin)
                                    {
                                        if (!demons1[i].Equals(demons2[j]) && !demons1[i].Equals(demons3[k]) && !demons3[k].Equals(demons2[j]))
                                        results.Add(new ResultToShow()
                                                        {
                                                            Demon1 = demons1[i],
                                                            Demon2 = demons2[j],
                                                            Demon3 = demons3[k],
                                                            Plus2 = "+",
                                                            Result = demon
                                                        });
                                    }
                                }
                                if (k == 0)
                                    break;
                            }
                            if (j == 0)
                                break;
                        }
                    }

                    rules2 = _rules2.FindAll(r => r.Result == rule3.Race2 && _racePrec[r.Race1] >= _racePrec[rule3.Race1] && _racePrec[r.Race2] >= _racePrec[rule3.Race1]);

                    foreach (UniteRule rule in rules2)
                    {
                        List<Demon> demons1 = _raceDemons[uniteRule.Race1];
                        List<Demon> demons2 = _raceDemons[rule.Race1];
                        List<Demon> demons3 = _raceDemons[rule.Race2];

                        for (int i = 0; i < demons1.Count; i++)
                        {
                            int j = 0;
                            for (; j < demons2.Count; j++)
                            {
                                int k = 0;
                                for (; k < demons3.Count; k++)
                                {
                                    int countLevel = (demons1[i].Level + demons2[j].Level + demons3[k].Level) / 3 + 4;
                                    if (countLevel > levelMax)
                                    {
                                        break;
                                    }
                                    if (countLevel > levelMin)
                                    {
                                        if (!demons1[i].Equals(demons2[j]) && !demons1[i].Equals(demons3[k]) && !demons3[k].Equals(demons2[j]))
                                        results.Add(new ResultToShow()
                                        {
                                            Demon1 = demons1[i],
                                            Demon2 = demons2[j],
                                            Demon3 = demons3[k],
                                            Plus2 = "+",
                                            Result = demon
                                        });
                                    }
                                }
                                if (k == 0)
                                    break;
                            }
                            if (j == 0)
                                break;
                        }
                    }
                    
                }
            }


            return results;
        }

        public Demon ClearEnemyOnlySkill(Demon demon)
        {
            if (!string.IsNullOrEmpty(demon.Skill2) && !_skills.Exists(s => s.CName == demon.Skill2))
                demon.Skill2 = string.Empty;
            if (!string.IsNullOrEmpty(demon.Skill3)&&!_skills.Exists(s => s.CName == demon.Skill3))
                demon.Skill3 = string.Empty;
            if (!string.IsNullOrEmpty(demon.Skill4) && !_skills.Exists(s => s.CName == demon.Skill4))
                demon.Skill4 = string.Empty;
            if (!string.IsNullOrEmpty(demon.Skill5) && !_skills.Exists(s => s.CName == demon.Skill5))
                demon.Skill5 = string.Empty;
            if (!string.IsNullOrEmpty(demon.Skill6) && !_skills.Exists(s => s.CName == demon.Skill6))
                demon.Skill6 = string.Empty;
            return demon;
        }

        public Demon AddSkill(Demon demon, Skill skill, string additional = null)
        {
            if (demon.Skills.Exists(s=>s == skill.CName || s.IndexOf(skill.CName+" (")==0))
                return demon;

            string addName;
            if (!string.IsNullOrEmpty(additional))
                addName = skill.CName + "(" + additional + ")";
            else
                addName = skill.CName;

            switch (demon.SkillCount)
            {
                case 0:
                    demon.Skill1 = addName;
                    break;
                case 1:
                    demon.Skill2 = addName;
                    break;
                case 2:
                    demon.Skill3 = addName;
                    break;
                case 3:
                    demon.Skill4 = addName;
                    break;
                case 4:
                    demon.Skill5 = addName;
                    break;
                case 5:
                    demon.Skill6 = addName;
                    break;
                default:
                    break;
            }
            return demon;
        }

        public Demon UniteSkill(Demon demon1, Demon demon2, Demon demon3, Demon result)
        {
            
            if(result!=null&&_skills!=null&&_skillInherts!=null)
            {
                result = ClearEnemyOnlySkill(result);

                if (result.SkillCount >= 6)
                    return result;

                if(!string.IsNullOrEmpty(result.InhertType))
                {
                    Demon result1 = result;
                    List<SkillInhert> skillInherts = _skillInherts.FindAll(i => i.InhertType == result1.InhertType);
                    if(skillInherts.Count>0)
                    {
                        skillInherts.Sort((i1,i2)=>i1.Rank.CompareTo(i2.Rank));
                        for (int i = 0; i < skillInherts.Count; i++)
                        {
                            Skill skill = _skills.Find(s => s.JName == skillInherts[i].SkillJName);
                            if (skill == null)
                                continue;
                            if(demon1!=null&&demon1.Skills!=null)
                            {
                                if (demon1.Skills.Contains(skill.CName))
                                {
                                    result = AddSkill(result, skill);
                                    continue;
                                }
                            }
                            if (demon2 != null && demon2.Skills != null)
                            {
                                if (demon2.Skills.Contains(skill.CName))
                                {
                                    result = AddSkill(result, skill);
                                    continue;
                                }
                            }
                            if (demon3 != null && demon3.Skills != null)
                            {
                                if (demon3.Skills.Contains(skill.CName))
                                {
                                    result = AddSkill(result, skill);
                                    continue;
                                }
                            }
                        }
                        
                    }
                }

                // goumaden skills
                if(result.SkillCount<6)
                {
                    List<SkillInhert> skillInherts = _skillInherts.FindAll(i => i.InhertType == "goumaden");
                    if (skillInherts.Count > 0)
                    {
                        skillInherts.Sort((i1, i2) => i1.Rank.CompareTo(i2.Rank));
                        for (int i = 0; i < skillInherts.Count; i++)
                        {
                            Skill skill = _skills.Find(s => s.JName == skillInherts[i].SkillJName);

                            if(skill==null)
                                continue;

                            if (demon1 != null && demon1.Skills != null)
                            {
                                if (demon1.Skills.Contains(skill.CName))
                                {
                                    result = AddSkill(result, skill, "g");
                                    continue;
                                }
                            }
                            if (demon2 != null && demon2.Skills != null)
                            {
                                if (demon2.Skills.Contains(skill.CName))
                                {
                                    result = AddSkill(result, skill, "g");
                                    continue;
                                }
                            }
                            if (demon3 != null && demon3.Skills != null)
                            {
                                if (demon3.Skills.Contains(skill.CName))
                                {
                                    result = AddSkill(result, skill, "g");
                                    continue;
                                }
                            }
                        }

                    }
                }

            }
            return result;
        }
    }
}
